#pragma once
//本文件实现运行功能 
#include "../comm/Util.hpp"
#include "../comm/Log.hpp"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/wait.h>
#include <time.h>
#include <sys/resource.h>
namespace ns_runner
{
    using namespace ns_util;
    using namespace ns_log;
    class Runner
    {
        public:
        Runner(){}
        ~Runner(){}
        //单位：cpu：毫秒，mem：MB
        static void SetLimit(int cpu,int mem)
        {
            struct rlimit cpu_limit;
            cpu_limit.rlim_max = RLIM_INFINITY;
            cpu_limit.rlim_cur = cpu;
            ::setrlimit(RLIMIT_CPU,&cpu_limit);

            struct rlimit mem_limit;
            mem_limit.rlim_max = RLIM_INFINITY;
            mem_limit.rlim_cur = mem * 1024;//为什么要乘1024，因为传进来的参数是KB单位，转换为字节
            ::setrlimit(RLIMIT_AS,&mem_limit);
        }
        static int Run(const std::string& filename,int cpu_limit,int mem_limit)
        {
            /**************************************
             * 程序运行
             * 1.代码跑完，结果正确
             * 2.代码跑完，结果不正确
             * 3.代码出异常
             * Run不考虑代码执行结果是否正确
             * 代码结果是否正确，由我们编写的测试用例来量度
             * 这个过程由oj版块决定
             * 返回值int
             * ret < 0 :程序运行失败，各种原因
             * ret > 0 :出异常了
             * 
             * 运行函数，必须知道要运行的程序在哪里
             * 对于这个程序：
             * 1.标准输入，默认是键盘，但是在Oj系统中，它被存储在一个文件中，我们设计时不考虑
             * 2.标准输出，默认输出到屏幕，需要重定向到一个文件
             * 3.标准错误，需要重定向，保存运行时的错误信息
             * 
             **************************************/
            //要执行的可执行程序:"1234"->"./temp/1234.exe"
            std::string execute   = PathUtil::Exe(filename);
            std::string _stdin    = PathUtil::Stdin(filename);
            std::string _stdout    = PathUtil::Stdout(filename);
            std::string _stderr    = PathUtil::Err(filename);

            //现在知道了这三个临时文件的名字，就要打开它们
            umask(0);
            LOG(INFO) << "打开三个标准文件" << std::endl;
            int in_fd = ::open(_stdin.c_str(),O_CREAT|O_RDONLY,0644);
            int out_fd = ::open(_stdout.c_str(),O_CREAT|O_WRONLY,0644);
            int err_fd = ::open(_stderr.c_str(),O_CREAT|O_WRONLY,0644);

            if(in_fd < 0 || out_fd < 0 || err_fd < 0)
            {
                LOG(ERROR) << "打开标准文件失败" << std::endl;
                return -1;
            }
            //现在打开了三个文件，子进程会继承父进程的文件描述符表
            //子进程需要重定向

            pid_t pid = fork();
            if(pid < 0)
            {
                LOG(ERROR) << "运行时创建子进程失败" << std::endl;
                ::close(in_fd);
                ::close(out_fd);
                ::close(err_fd);
                return -2;
            }
            else if (pid == 0)
            {
                ::dup2(in_fd,0);
                ::dup2(out_fd,1);
                ::dup2(err_fd,2);
                //设置程序运行时的内存和cpu占用限制
                SetLimit(cpu_limit,mem_limit);
                //子进程做程序替换，执行可执行程序
                ::execlp(execute.c_str(),execute.c_str(),nullptr);

                //退出码为什么是1，因为如果执行到这行，一定是程序替换失败了，父进程对这种情况其实不处理
                exit(1);
            }
            else
            {
                ::close(in_fd);
                ::close(out_fd);
                ::close(err_fd);
                //父进程等待子进程

                int status;
                ::waitpid(pid,&status,0);
                //为什么是按位与，低16位中，高8位是退出码，低7位是异常编号
                LOG(INFO) << "程序运行完毕，异常编号是" << (status & 0x7F) << "\n";
                return status& 0x7F;//返回异常编号
            }
        }
    };
}